using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Diagnostics;
using System.Globalization;
using System.Xml;
using System.IO;
using WindowsLive.Writer.Api;
using WindowsLive.Writer.CoreServices;
using WindowsLive.Writer.BlogClient;
using WindowsLive.Writer.BlogClient.Clients;
using WindowsLive.Writer.Extensibility.BlogClient;
using WindowsLive.Writer.PostEditor;

namespace Clarius.WindowsLive.Writer.CrossPostPlugin
{
	[WriterPlugin(
		CrossPost.Guid,
		"Cross-post",
		"CrossPost.png",
		Description = "Inserts a new blog post in another blog, based on and linking to the current one. Unless the plug-in options are changed, creates a summary and shows a preview before posting.",
		HasEditableOptions = true,
		PublisherUrl = "http://www.cazzulino.com")]
	[InsertableContentSource("Cross-post", SidebarText = "cross-post")]
	public class CrossPost : ContentSource
	{
		public const string Guid = "b98c057d-f423-454b-9a1c-d9a3e455b86b";
		FieldInfo contextField = typeof(PostEditorForm).GetField("_initialEditingContext", BindingFlags.NonPublic | BindingFlags.Instance);
		FieldInfo editorControlField = typeof(PostEditorForm).GetField("_postEditorMainControl", BindingFlags.NonPublic | BindingFlags.Instance);
		PropertyInfo editingManagerField = typeof(PostEditorForm).Assembly.GetType("WindowsLive.Writer.PostEditor.PostEditorMainControl", true).GetProperty("BlogPostEditingManager", BindingFlags.NonPublic | BindingFlags.Instance);
		FieldInfo managerPostField = typeof(BlogPostEditingManager).GetField("_blogPost", BindingFlags.NonPublic | BindingFlags.Instance);

		public override void EditOptions(IWin32Window dialogOwner)
		{
			new EditOptionsForm().ShowDialog(dialogOwner);
		}

		public override DialogResult CreateContent(IWin32Window dialogOwner, ref string content)
		{
			PostEditorForm form = (PostEditorForm)dialogOwner;
			// I can't use this context because it does not contain the actual 
			// published entry, but a temporary "new" one with just the copied content.
			IBlogPostEditingContext ctx = (IBlogPostEditingContext)contextField.GetValue(form);

			// This is the real holder of the one true post ;)
			object editorControl = editorControlField.GetValue(form);
			// The editing manager keeps track of the state of the real post.
			BlogPostEditingManager editingManager = (BlogPostEditingManager)editingManagerField.GetValue(editorControl, null);
			BlogPost sourcePostClone = (BlogPost)((BlogPost)managerPostField.GetValue(editingManager)).Clone();

			if (editingManager.PostIsDraft || editingManager.PostIsDirty || sourcePostClone.IsNew)
			{
				MessageBox.Show(dialogOwner,
					"Current blog post must be published before cross-posting.",
					ThisAssembly.AssemblyDescription);
				return DialogResult.Cancel;
			}

			string targetBlogId =
				ApplicationEnvironment.UserSettingsRoot.GetString(
					EditOptionsForm.TargetBlogIdOption, null);
			if (targetBlogId == null)
			{
				BlogDescriptor[] allBlogs = BlogSettings.GetBlogs(true);
				if (allBlogs.Length == 2)
				{
					if (allBlogs[0].Id == form.CurrentAccountId)
						targetBlogId = allBlogs[1].Id;
					else
						targetBlogId = allBlogs[0].Id;
				}
				else
				{
					MessageBox.Show(dialogOwner,
						"More than 2 blogs are configured. Set through the plug-in options which should be the target for cross-posting moved entries.",
						ThisAssembly.AssemblyDescription);
					return DialogResult.Cancel;
				}
			}

			bool postSummary = ApplicationEnvironment.UserSettingsRoot.GetBoolean(
				EditOptionsForm.PostSummaryOption, true);
			bool previewPost = ApplicationEnvironment.UserSettingsRoot.GetBoolean(
				EditOptionsForm.PreviewPostOption, true);

			BlogPost crossPost = new BlogPost();
			// Get the contents from the actually posted blog entry, so that all 
			// images are referenced by full or relative paths to the actual publish location.
			string originalPostedContent = BlogClientManager.
					CreateClient(BlogSettings.ForBlogId(form.CurrentAccountId)).
						GetPost(form.CurrentAccountId, sourcePostClone.Id).Contents;

			sourcePostClone.Contents = FixRelativeUrls(BlogSettings.ForBlogId(form.CurrentAccountId), originalPostedContent);
			crossPost.Contents = postSummary ? Summarize(sourcePostClone) : MovedWarning(sourcePostClone);
			crossPost.Title = sourcePostClone.Title;
			crossPost.Categories = sourcePostClone.Categories;

			if (previewPost)
			{
				PostEditorForm.Launch(
					new BlogPostEditingContext(targetBlogId, crossPost));
			}
			else
			{
				BlogSettings targetBlogSettings = BlogSettings.ForBlogId(targetBlogId);
				IBlogClient targetBlogclient = BlogClientManager.CreateClient(targetBlogSettings);
				string etag;
				XmlDocument postedDoc;
				string crossPostedId = targetBlogclient.NewPost(targetBlogSettings.HostBlogId, crossPost,
					NullNewCategoryContext.Instance, true, out etag, out postedDoc);
				BlogPost createdPost = targetBlogclient.GetPost(targetBlogSettings.HostBlogId, crossPostedId);

				string postUri = createdPost.Permalink;
				if (!Uri.IsWellFormedUriString(postUri, UriKind.Absolute))
				{
					// make absolute from blog homepage.
					postUri = new Uri(
						new Uri(targetBlogSettings.HomepageUrl),
						new Uri(createdPost.Permalink, UriKind.Relative)).ToString();
				}

				ProcessStartInfo start = new ProcessStartInfo(postUri);
				start.UseShellExecute = true;
				Process.Start(start);
			}

			return DialogResult.OK;
		}

		private string FixRelativeUrls(BlogSettings settings, string contents)
		{
			string baseUri = new Uri(settings.HomepageUrl).GetLeftPart(UriPartial.Authority);

			string result = contents.Replace("href=\"/", "href=\"" + baseUri + "/");
			result = result.Replace("src=\"/", "src=\"" + baseUri + "/");

			return result;
		}

		private static string MovedWarning(BlogPost sourcePost)
		{
			return "<p>" + String.Format(
				CultureInfo.CurrentCulture,
				Properties.Resources.EntryMoved,
				sourcePost.Permalink) + "</p>" +
				sourcePost.Contents;
		}

		private string Summarize(BlogPost sourcePost)
		{
			string readMore = String.Format(
				CultureInfo.CurrentCulture,
				Properties.Resources.ReadMore,
				sourcePost.Permalink);
			string moreDots = String.Format(
				CultureInfo.CurrentCulture,
				Properties.Resources.MoreDots,
				sourcePost.Permalink);
			int summaryChars = ApplicationEnvironment.UserSettingsRoot.GetInt32(
				EditOptionsForm.SummaryCharactersOption, 200);

			StringBuilder output = new StringBuilder();
			XmlWriterSettings set = new XmlWriterSettings();
			set.OmitXmlDeclaration = true;
			using (XmlWriter writer = XmlWriter.Create(output, set))
			{
				Sgml.SgmlReader reader = new Sgml.SgmlReader();
				reader.InputStream = new StringReader("<p>" + sourcePost.Contents + "</p>");
				SummaryReader sr = new SummaryReader(reader, summaryChars, moreDots);
				writer.WriteNode(sr, false);
			}

			string html = output.ToString() + "<p>" + readMore + "</p>";

			return html;
		}

		class SummaryReader : XmlWrappingReader
		{
			int summaryChars;
			int readChars;
			bool skip;
			InitialDepthXmlReader moreDotsReader;
			XmlReader originalReader;

			public SummaryReader(XmlReader baseReader, int summaryChars, string moreDots)
				: base(baseReader)
			{
				this.summaryChars = summaryChars;
				moreDotsReader = new InitialDepthXmlReader(XmlReader.Create(new StringReader(moreDots)));
				moreDotsReader.MoveToContent();
			}

			public override bool Read()
			{
				if (skip)
				{
					if (BaseReader == moreDotsReader)
					{
						if (BaseReader.Read())
						{
							return true;
						}
						else
						{
							BaseReader = originalReader;
						}
					}
					else if (!moreDotsReader.EOF)
					{
						moreDotsReader.initialDepth = Depth;
						originalReader = BaseReader;
						BaseReader = moreDotsReader;
						return true;
					}

					int closeDepth = Depth - 1;
					bool read = BaseReader.Read();
					if (read)
					{
						while (Depth >= closeDepth)
						{
							if (!BaseReader.Read())
								return false;
						}
					}
					return read;
				}
				else
				{
					bool read = base.Read();
					if (read && NodeType == XmlNodeType.Text)
					{
						readChars += this.Value.Length;
						if (readChars > summaryChars)
							skip = true;
					}
					return read;
				}
			}
		}

		class InitialDepthXmlReader : XmlWrappingReader
		{
			internal int initialDepth;

			public InitialDepthXmlReader(XmlReader baseReader)
				: base(baseReader)
			{
			}

			public override int Depth
			{
				get
				{
					return base.Depth + initialDepth;
				}
			}
		}

		class NullNewCategoryContext : INewCategoryContext
		{
			public static NullNewCategoryContext Instance = new NullNewCategoryContext();

			public void NewCategoryAdded(BlogPostCategory category)
			{
			}
		}
	}
}
